package lnk

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/olekukonko/tablewriter"
)

// PropertyStoreDataBlock
// BlockSize (4 bytes): A 32-bit, unsigned integer that specifies the size of the PropertyStoreDataBlock structure. This value MUST be greater than or equal to 0x0000000C.
// BlockSignature (4 bytes): A 32-bit, unsigned integer that specifies the signature of the PropertyStoreDataBlock extra data section. This value MUST be 0xA0000009.
// PropertyStore (variable): A serialized property storage structure ([MS-PROPSTORE] section 2.2).
type PropertyStoreDataBlock struct {
	size          uint32
	signature     uint32
	PropertyStore SerializedPropertyStore
}

func (t *PropertyStoreDataBlock) BlockSize() uint32 {
	return t.size
}

func (t *PropertyStoreDataBlock) BlockSignature() uint32 {
	return t.signature
}

func (t *PropertyStoreDataBlock) BlockType() string {
	return blockSignature(t.signature)
}

// String prints the TrackerDataBlock in a table.
func (t PropertyStoreDataBlock) String() string {
	var sb strings.Builder
	sb.WriteString("\n")
	table := tablewriter.NewWriter(&sb)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetColWidth(200)
	table.SetRowLine(true)

	table.SetHeader([]string{"PropertyStoreDataBlock", "Value"})

	for _, storage := range t.PropertyStore.SerializedPropertyStorage {
		table.Append([]string{"FormatID", storage.FormatID.String()})
		for _, strValue := range storage.SerializePropertyValueString {
			if d, ok := strValue.Value.Value.(Dump); ok {
				table.Append([]string{strValue.Name, fmt.Sprintf("type: 0x%04x", strValue.Value.Type)})
				table.Append([]string{fmt.Sprintf("%s", d)})
			} else {
				table.Append([]string{strValue.Name, fmt.Sprintf("0x%04x: %v", strValue.Value.Type, strValue.Value.Value)})
			}
		}
		for _, intValue := range storage.SerializePropertyValueInteger {
			if d, ok := intValue.Value.Value.(Dump); ok {
				table.Append([]string{strconv.Itoa(int(intValue.Id)), fmt.Sprintf("type: 0x%04x", intValue.Value.Type)})
				table.Append([]string{"", fmt.Sprintf("%s", d)})
			} else {
				table.Append([]string{strconv.Itoa(int(intValue.Id)), fmt.Sprintf("0x%04x, %v", intValue.Value.Type, intValue.Value.Value)})
			}
		}
	}
	table.Render()

	return sb.String()
}

// SerializedPropertyStore
// StoreSize (4 bytes): An unsigned integer that specifies the total size, in bytes, of this structure, excluding the size of this field.
// SerializedPropertyStorage (variable): A sequence of one or more Serialized Property Storage structures, as specified in section 2.2.
type SerializedPropertyStore struct {
	StoreSize                 uint32
	SerializedPropertyStorage []SerializedPropertyStorage
}

// SerializedPropertyStorage
// StorageSize (4 bytes): An unsigned integer that specifies the total size, in bytes, of this structure. It MUST be 0x00000000 if this is the last Serialized Property Storage in the enclosing Serialized Property Store.
// Version (4 bytes): Has to be equal to 0x53505331.
// FormatID (16 bytes): A GUID that specifies the semantics and expected usage of the properties contained in this Serialized Property Storage structure. It MUST be unique in the set of serialized property storage structures.
// SerializedPropertyValue (variable): A sequence of one or more property values. If the Format ID field is equal to the GUID {D5CDD505-2E9C-101B-9397-08002B2CF9AE}, then all values in the sequence MUST be Serialized Property Value (String Name) structures, as specified in section 2.3.1; otherwise, all values MUST be Serialized Property Value (Integer Name) structures, as specified in section 2.3.2. The last Serialized Property Value in the sequence MUST specify 0x00000 for the Value Size.
type SerializedPropertyStorage struct {
	StorageSize                   uint32
	Version                       uint32
	FormatID                      GUID
	SerializePropertyValueString  []SerializedPropertyValueStringName
	SerializePropertyValueInteger []SerializedPropertyValueIntegerName
}

//SerializedPropertyValueStringName
//Value Size (4 bytes): An unsigned integer that specifies the total size, in bytes, of this structure. It MUST be 0x00000000 if this is the last The Serialized Property Value in the enclosing Serialized Property Storage structure.
//Name Size (4 bytes): An unsigned integer that specifies the size, in bytes, of the Name field, including the null-terminating character.
//Reserved (1 byte): Has to be 0x00.
//Name (variable): A null-terminated Unicode string that specifies the identity of the property. It has to be unique within the enclosing Serialized Property Storage structure.
//Value (variable): A TypedPropertyValue structure, as specified in [MS-OLEPS] section 2.15
type SerializedPropertyValueStringName struct {
	ValueSize uint32
	NameSize  uint32
	Reserved  byte
	Name      string
	Value     TypedPropertyValue
}

//SerializedPropertyValueIntegerName
//Value Size (4 bytes): An unsigned integer that specifies the total size, in bytes, of this structure. It MUST be 0x00000000 if this is the last Serialized Property Value in the enclosing Serialized Property Storage structure.
//Id (4 bytes): An unsigned integer that specifies the identity of the property. It MUST be unique within the enclosing Serialized Property Storage structure.
//Reserved (1 byte): MUST be 0x00.
//Value (variable): A TypedPropertyValue structure, as specified in [MS-OLEPS] section 2.15
type SerializedPropertyValueIntegerName struct {
	ValueSize uint32
	Id        uint32
	Reserved  byte
	Value     TypedPropertyValue
}

func ParsePropertyStoreDataBlock(size, signature uint32, data []byte) (*PropertyStoreDataBlock, error) {
	r := bytes.NewBuffer(data)
	d := PropertyStoreDataBlock{
		size:      size,
		signature: signature,
	}

	for {
		storage := SerializedPropertyStorage{}

		err := binary.Read(r, binary.LittleEndian, &storage.StorageSize)
		if err != nil {
			return nil, err
		}

		if storage.StorageSize == 0 {
			break
		}

		//fmt.Printf("storage size: %d\n", storage.StorageSize)

		err = binary.Read(r, binary.LittleEndian, &storage.Version)
		if err != nil {
			return nil, err
		}

		//fmt.Printf("Version: 0x%08x\n", storage.Version)

		guidByte := new([16]byte)
		_, err = io.ReadFull(r, guidByte[:])
		if err != nil {
			return nil, err
		}
		storage.FormatID = makeGuidWithBytes(*guidByte)
		//fmt.Println(storage.FormatID)

		if storage.FormatID.String() == "D5CDD505-2E9C-101B-9397-08002B2CF9AE" { //StringValue
			for {
				stringName := SerializedPropertyValueStringName{}

				err = binary.Read(r, binary.LittleEndian, &stringName.ValueSize)
				if err != nil {
					return nil, err
				}
				if stringName.ValueSize == 0 {
					break
				}
				//fmt.Printf("ValueSize: %d\n", stringName.ValueSize)

				err = binary.Read(r, binary.LittleEndian, &stringName.NameSize)
				if err != nil {
					return nil, err
				}
				//fmt.Printf("NameSize: %d\n", stringName.NameSize)
				stringName.Reserved, err = r.ReadByte()
				if err != nil {
					return nil, err
				}
				//fmt.Printf("Reserved: %d 0x%b\n", stringName.Reserved, stringName.Reserved)

				nameBytes := make([]byte, stringName.NameSize)
				_, err = io.ReadFull(r, nameBytes)
				if err != nil {
					return nil, err
				}
				stringName.Name = readUnicodeString(nameBytes)

				data := make([]byte, stringName.ValueSize-9-stringName.NameSize)
				_, err = io.ReadFull(r, data)
				if err != nil {
					return nil, err
				}

				stringName.Value, err = ParseTypedPropertyValue(data)
				storage.SerializePropertyValueString = append(storage.SerializePropertyValueString, stringName)
			}
		} else { //IntegerValue
			for {
				integerName := SerializedPropertyValueIntegerName{}

				err = binary.Read(r, binary.LittleEndian, &integerName.ValueSize)
				if err != nil {
					return nil, err
				}
				if integerName.ValueSize == 0 {
					break
				}
				//fmt.Printf("ValueSize: %d 0x%08x\n", integerName.ValueSize, integerName.ValueSize)

				err = binary.Read(r, binary.LittleEndian, &integerName.Id)
				if err != nil {
					return nil, err
				}
				//fmt.Printf("Id: %d 0x%08x\n", integerName.Id, integerName.Id)

				integerName.Reserved, err = r.ReadByte()
				if err != nil {
					return nil, err
				}
				//fmt.Printf("Reserved: %d 0x%b\n", integerName.Reserved, integerName.Reserved)
				data := make([]byte, integerName.ValueSize-9)
				_, err = io.ReadFull(r, data)

				integerName.Value, err = ParseTypedPropertyValue(data)
				storage.SerializePropertyValueInteger = append(storage.SerializePropertyValueInteger, integerName)
			}
		}
		d.PropertyStore.SerializedPropertyStorage = append(d.PropertyStore.SerializedPropertyStorage, storage)

	}

	return &d, nil
}
